#ifndef COLOR_PRINTF_H_
#define COLOR_PRINTF_H_

#include <cstdlib>
#include <cstdarg>

#include <sstream>
#include <iostream>

#include <Windows.h>


#ifndef ENUM_AND_NAME
#   define ENUM_AND_NAME(item)   {item, #item}
#else
#   error the ENUM_AND_NAME has already been defined.
#endif


/// \brief This enum type represent all the color which could be used.
enum ConsoleColor
{
    DefaultColor = 9,
    DarkRed      = 0,   // the start element
    DarkGreen,
    DarkBlue,
    Red,
    Green,
    Blue,
    DarkCyan,
    DarkMagenta,
    DarkYellow,
    Gray,
    Cyan,
    Magenta,
    Yellow,
    White               // the end element
};


struct _ColorAttribute
{
    ConsoleColor color;
    WORD         value;
};


struct _ColorName
{
    ConsoleColor color;
    char*        name;
};


// the "colorTable" should has the same sequence as "enum ConsoleColor"
const _ColorAttribute colorTable[] =
{
    {DarkRed,     FOREGROUND_RED   },
    {DarkGreen,   FOREGROUND_GREEN },
    {DarkBlue,    FOREGROUND_BLUE  },
    {Red,         FOREGROUND_RED   | FOREGROUND_INTENSITY },
    {Green,       FOREGROUND_GREEN | FOREGROUND_INTENSITY },
    {Blue,        FOREGROUND_BLUE  | FOREGROUND_INTENSITY },
    {DarkCyan,    FOREGROUND_BLUE  | FOREGROUND_GREEN     },
    {DarkMagenta, FOREGROUND_BLUE  | FOREGROUND_RED       },
    {DarkYellow,  FOREGROUND_RED   | FOREGROUND_GREEN     },
    {Gray,        FOREGROUND_BLUE  | FOREGROUND_RED       | FOREGROUND_GREEN },
    {Cyan,        FOREGROUND_BLUE  | FOREGROUND_GREEN     | FOREGROUND_INTENSITY},
    {Magenta,     FOREGROUND_BLUE  | FOREGROUND_RED       | FOREGROUND_INTENSITY},
    {Yellow,      FOREGROUND_RED   | FOREGROUND_GREEN     | FOREGROUND_INTENSITY},
    {White,       FOREGROUND_BLUE  | FOREGROUND_RED       | FOREGROUND_GREEN | FOREGROUND_INTENSITY}
};


const _ColorName colorNameTable[] =
{
    ENUM_AND_NAME(DarkRed),
    ENUM_AND_NAME(DarkGreen),
    ENUM_AND_NAME(DarkBlue),
    ENUM_AND_NAME(Red),
    ENUM_AND_NAME(Green),
    ENUM_AND_NAME(Blue),
    ENUM_AND_NAME(DarkCyan),
    ENUM_AND_NAME(DarkMagenta),
    ENUM_AND_NAME(DarkYellow),
    ENUM_AND_NAME(Gray),
    ENUM_AND_NAME(Cyan),
    ENUM_AND_NAME(Magenta),
    ENUM_AND_NAME(Yellow),
    ENUM_AND_NAME(White)
};


inline const char* getColorName(ConsoleColor color)
{
    if ((color < DarkRed) || (color > White))
    {
        return NULL;
    }

    return colorNameTable[color - DarkRed].name;
}


inline int _vprintf(ConsoleColor color, const char* fmt, va_list args)
{
    if ((DarkRed > color) || (White < color))
    {
        return NULL;
    }

    HANDLE hConsole = NULL;
    CONSOLE_SCREEN_BUFFER_INFO consoleInfo = {0};
    WORD wColor = 0;
    hConsole = GetStdHandle(STD_OUTPUT_HANDLE);

    // get current console text's color
    GetConsoleScreenBufferInfo(hConsole, &consoleInfo);
    wColor = colorTable[color-DarkRed].value;

    // set console text color
    fflush(stdout);
    SetConsoleTextAttribute(hConsole, wColor);

    // print text
    int num = vprintf(fmt, args);

    // restore console text color
    fflush(stdout);
    SetConsoleTextAttribute(hConsole, consoleInfo.wAttributes);

    return num;
}


// print text with color
inline int printf(ConsoleColor color, const char* fmt, ...)
{
    va_list args;
    va_start(args, fmt);

    int num = _vprintf(color, fmt, args);

    va_end(args);

    return num;
}


#endif
